/**
  ******************************************************************************
  * @file         ymodem.c
  * @brief        本模块实现了Ymodem文件传输协议的接收功能
  * @author       Leo
  * @date         2021-8-25
  * @version      v3.1
  * @copyright    dxtc  鼎新同创·智能锁
  * 
  * @note         Ymodem协议传输文件的流程如下（例：发送Leo.c，文件长度1064字节）
  * 
  *               发送端                                          接收端
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    C
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    C
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ...
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    C （重复发C，直到发送端开始发出文件）
  *               SOH 00 FF "Leo.c" NUL "1064" NUL[118] CRC CRC >>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ACK
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    C
  *               STX 01 FE data[1024] CRC CRC>>>>>>>>>>>>>>>>>>>>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ACK
  *               SOH 05 FA data[100]  1A[28] CRC CRC>>>>>>>>>>>>>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ACK
  *               EOT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    NAK
  *               EOT>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ACK
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    C
  *               SOH 00 FF NUL[128] CRC CRC >>>>>>>>>>>>>>>>>>>>>>>>>
  *               <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    ACK
  *
  *******************************************************************************
  */
#include "component.h"
#include "device.h"

#define YMODEM_LOG(format, ...) printf( "[ymodem.c] " format , ##__VA_ARGS__)
#define __YMODEM_LOG(format, ...) printf( format , ##__VA_ARGS__)

//TASK事件
#define EVENT_YM_TIMEOUT        (0x00000001)
#define EVENT_YM_SEND_C         (0x00000002)
#define EVENT_YM_SEND_ACK       (0x00000004)


//YMODEM PARAM
#define YM_PACKET_LEN_SOH       (128)
#define YM_PACKET_LEN_STX       (1024)
#define YM_SOH                  (0x01)  /* start of 128-byte data packet */
#define YM_STX                  (0x02)  /* start of 1024-byte data packet */
#define YM_ETX                  (0x03)  /* end of Text */
#define YM_EOT                  (0x04)  /* end of transmission */
#define YM_ACK                  (0x06)  /* acknowledge */
#define YM_NAK                  (0x15)  /* negative acknowledge */
#define YM_CA                   (0x18)  /* two of these in succession aborts transfer */
#define YM_C                    (0x43)  /* 'C' == 0x43, request */

#define YMODEM_RX_BUFF_LEN  (YM_PACKET_LEN_STX + 10)  

#ifndef KOS_PARAM_YMODEM_ACK_TIME
#define KOS_PARAM_YMODEM_ACK_TIME   (2)
#endif

#ifndef KOS_PARAM_YMODEM_TIMEOUT
#define KOS_PARAM_YMODEM_TIMEOUT      (200)
#endif 


#if defined(KOS_PARAM_YMODEM_SLOW)
static FlagStatus ymodem_slow_mode = SET;   //使能低速模式
#else
static FlagStatus ymodem_slow_mode = RESET; //禁能低速模式（快速模式，边接收ymodem边写flash）
#endif

static VirtualHardware_enum_t v_uart = vUART_5; //串口设备
static RingBufferHandle_t rx_handle = NULL;     //接收缓存句柄
static bool dfu_auto_ack = true;                   //自动回送ack
static uint32_t YmodemAckTime = KOS_PARAM_YMODEM_ACK_TIME;
/* ymodem send info */
static struct
{
    uint8_t cmd1;
    uint8_t cmd2;
    uint8_t resend_times;
}ymodem_send_info;

/* ymodem rx data struct */
static struct
{
    union
    {
        uint32_t buffer;
        uint8_t  head[4];
    }packet_head;
    
    enum
    {
        YM_STA_IDLE = 0,//状态空闲
        YM_STA_RX_NAME, //正在接收文件名（首包）
        YM_STA_RX_DATA, //正在接收文件数据
        YM_STA_RX_EOT1, //正在接收第一个结束符
        YM_STA_RX_EOT2, //正在接收第二个结束符
        YM_STA_RX_END,  //正在接收结束帧
    }status;

    uint32_t rx_len;                        //已接收的数据长度
    uint32_t file_len;                      //当前正在接收的这个文件的总长度
    uint8_t  file_name[YM_PACKET_LEN_SOH];  //当前正在接收的文件名

    uint16_t packet_size;                   //当前正在接收的这个包的SIZE
    uint8_t  packet_data[YM_PACKET_LEN_STX];//当前正在接收的这个包的数据
}ym_rx_data;



/**
  * @brief  发送数据给应用层
  * @param  psn：文件分包编号
  *         psn为FFFD：表示数据域为文件名和文件长度（ASCII）
  *         psn为FFFF：表示文件传输结束（正常结束）
  *         psn为FFFE：表示文件传输结束（ymodem出错）
  *         其他值：正常的数据包编号
  * 
  * @param  data：数据内容
  * @param  size：数据包长度
  */
static void Ymodem_PublishToApp(uint16_t psn, uint8_t *data, uint16_t size)
{
    YmodemMsg_stu_t *ymode_msg = NULL;
    
    ymode_msg = (YmodemMsg_stu_t *)OSAL_Malloc(sizeof(YmodemMsg_stu_t) + size);
    if (ymode_msg != NULL)
    {
        ymode_msg->psn = psn;
        ymode_msg->size = size;
        memcpy(ymode_msg->data, data, size);
        OSAL_MessagePublish(ymode_msg, sizeof(YmodemMsg_stu_t) + size);
        OSAL_Free(ymode_msg);
    }
}

/**
  * @brief  计算YMODEM-CRC16
  * @note   
  */
static uint16_t Ymodem_CalcCRC(uint8_t *data, uint16_t len)
{
    unsigned short CRC = 0;
    unsigned short i = 0;

    while (len--)  //len是所要计算的长度
    {
        CRC = CRC^(int)(*data++) << 8;
        for (i=8; i!=0; i--) 
        {
            if (CRC & 0x8000)
            {
                CRC = CRC << 1 ^ 0x1021;    
            }
            else
            {
                CRC = CRC << 1;
            }
        }
    }
    return CRC;
}

/**
  * @brief  中止Ymodem传输
  *
  * @note   传输产生错误或者应用层调用STOP中止传输
  * 
  * @param   type：中止类型
  *          0：应用层中止传输
  *          1：CRC出错
  *          2：超时错误
  */
static void Ymodem_Abort(uint8_t type)
{
    /* Send abort cmd */
    uint8_t cmd = YM_NAK;
    Device_Write(v_uart, &cmd, 1, 0);
    cmd = YM_CA;
    Device_Write(v_uart, &cmd, 1, 0);
    cmd = YM_CA;
    Device_Write(v_uart, &cmd, 1, 0);

    /* Delete events */
    OSAL_EventDelete(COMP_YMODEM, EVENT_YM_SEND_C);
    OSAL_EventDelete(COMP_YMODEM, EVENT_YM_TIMEOUT);

    /* Clean data, publish msg to app*/
    memset(&ym_rx_data, 0, sizeof(ym_rx_data));
    if (type != 0)
    {
        Ymodem_PublishToApp(0xFFFE, NULL, 0);
    }
}

/**
  * @brief  发出Ymodem命令
  *
  * @param  cmd：YM_ACK、YM_NAK、YM_C
  * @note   发出命令后，会创建定时事件用来做超时逻辑
  */
static void Ymodem_SendCmd(uint8_t cmd1, uint8_t cmd2)
{
    YMODEM_LOG("Send cmd:0x%X\r\n", cmd1);
    Device_Write(v_uart, &cmd1, 1, 0);
    ymodem_send_info.cmd1 = cmd1;

    if (cmd2 != 0)
    {
        YMODEM_LOG("Send cmd:0x%X\r\n", cmd2);
        Device_Write(v_uart, &cmd2, 1, 0);
        ymodem_send_info.cmd2 = cmd2;
    }

    /* 每次发出一个命令，都刷新一下等待的超时时间 */
    OSAL_EventSingleCreate(COMP_YMODEM, EVENT_YM_TIMEOUT, KOS_PARAM_YMODEM_TIMEOUT, EVT_PRIORITY_MEDIUM);
}

/**
  * @brief  从RX环形缓存解析收到的数据
  * @note   
  */
static void Ymodem_ParseRxData(void)
{
    if (ym_rx_data.status == YM_STA_IDLE)
    {
        OSAL_RingBufferReset(rx_handle);
        return;
    }

    while (ym_rx_data.packet_size == 0)
    {
        uint8_t tmpData = 0;
        
        if (OSAL_RingBufferRead(rx_handle, &tmpData) != SUCCESS)
        {
            break;
        }

        if ((ym_rx_data.status == YM_STA_RX_EOT1) && (tmpData == YM_EOT || tmpData == YM_ETX))
        {
            YMODEM_LOG("Rx EOT\r\n");
            memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
            Ymodem_SendCmd(YM_NAK, 0);
            ym_rx_data.status = YM_STA_RX_EOT2;
        }
        else if ((ym_rx_data.status == YM_STA_RX_EOT2) && (tmpData == YM_EOT || tmpData == YM_ETX))
        {
            YMODEM_LOG("Rx EOT\r\n");
            memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
            Ymodem_SendCmd(YM_ACK, YM_C);
            ym_rx_data.status = YM_STA_RX_END;
        }
        else
        {
            ym_rx_data.packet_head.buffer <<= 8;
            ym_rx_data.packet_head.buffer |= tmpData;
            if (((~(ym_rx_data.packet_head.head[0]) & 0XFF) == ym_rx_data.packet_head.head[1]) && (ym_rx_data.packet_head.head[2] == YM_SOH))
            {
                ym_rx_data.packet_size = YM_PACKET_LEN_SOH;
                YMODEM_LOG("Rx SOH: %d\r\n", YM_PACKET_LEN_SOH);
            }
            else if (((~(ym_rx_data.packet_head.head[0]) & 0XFF) == ym_rx_data.packet_head.head[1]) && (ym_rx_data.packet_head.head[2] == YM_STX))
            {
                ym_rx_data.packet_size = YM_PACKET_LEN_STX;
                YMODEM_LOG("Rx STX: %d\r\n", YM_PACKET_LEN_STX);
            }
        }
    }

    if ((ym_rx_data.packet_size != 0) && (OSAL_RingBufferGetValidSize(rx_handle) >= (ym_rx_data.packet_size + 2)))
    {
        uint8_t crc_h, crc_l;

        for (int i = 0; i < ym_rx_data.packet_size; i++)
        {
            OSAL_RingBufferRead(rx_handle, &(ym_rx_data.packet_data[i]));
        }
        OSAL_RingBufferRead(rx_handle, &crc_h);
        OSAL_RingBufferRead(rx_handle, &crc_l);

        YMODEM_LOG("Rx psn:%d\r\n", ym_rx_data.packet_head.head[1]);

    #if 0 //打印收到的文件内容
        for (int i=0; i<ym_rx_data.packet_size; i++)
        {
            if (i % 16 == 0)
            {
                printf("\r\n");
            }
            printf("%02X ", ym_rx_data.packet_data[i]);
        }
        printf("\r\n");
        printf("recv crc:%02X%02X\r\n", crc_h, crc_l);
        printf("calc crc:%04X\r\n", Ymodem_CalcCRC(ym_rx_data.packet_data, ym_rx_data.packet_size));
    #endif

        if (Ymodem_CalcCRC(ym_rx_data.packet_data, ym_rx_data.packet_size) == ((crc_h << 8) | crc_l))
        {
            if (ym_rx_data.status == YM_STA_RX_END)
            {
                YMODEM_LOG("Transmission end\r\n");
                memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
                Ymodem_SendCmd(YM_ACK, 0);
                memset(&ym_rx_data, 0, sizeof(ym_rx_data));

                /* 传输结束：删掉超时事件、通知应用层 */
                OSAL_EventDelete(COMP_YMODEM, EVENT_YM_TIMEOUT);
                Ymodem_PublishToApp(0xFFFF, NULL, 0);
            }
            else if (ym_rx_data.status == YM_STA_RX_NAME)
            {
                ym_rx_data.status = YM_STA_RX_DATA;
                memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
                Ymodem_SendCmd(YM_ACK, YM_C);

                /* 收到文件：停止周期发送C */
                OSAL_EventDelete(COMP_YMODEM, EVENT_YM_SEND_C);

                /* 解析文件名和文件长度、PUBLISH到APP */
                strcpy((char*)ym_rx_data.file_name, (char*)ym_rx_data.packet_data);
                ym_rx_data.file_len = atoi((char*)&ym_rx_data.packet_data[strlen((char*)ym_rx_data.file_name) + 1]);
                YMODEM_LOG("File name: %s\r\n", ym_rx_data.file_name);
                YMODEM_LOG("File size: %d Byte\r\n", ym_rx_data.file_len);
                Ymodem_PublishToApp(0xFFFD, ym_rx_data.packet_data, ym_rx_data.packet_size);
            }
            else
            {
                ym_rx_data.rx_len += ym_rx_data.packet_size;
                if (ym_rx_data.rx_len >= ym_rx_data.file_len)
                {
                    ym_rx_data.packet_size -= ym_rx_data.rx_len - ym_rx_data.file_len;
                    ym_rx_data.status = YM_STA_RX_EOT1;
                    YMODEM_LOG("File receive completed\r\n");
                    dfu_auto_ack = true;
                }
                Ymodem_PublishToApp(ym_rx_data.packet_head.head[1], ym_rx_data.packet_data, ym_rx_data.packet_size);

                if (ymodem_slow_mode == SET)
                {
                    /* 低速模式：收到数据包后，延后发出ACK（为了防止写FLASH时导致RX丢包） */
                    /* 一些情况下, 不要自动回送ACK; 使用Ymodem_SendAck()代替 */
                    if ( dfu_auto_ack ) 
                        OSAL_EventSingleCreate(COMP_YMODEM, EVENT_YM_SEND_ACK, YmodemAckTime, EVT_PRIORITY_MEDIUM);                  
                    OSAL_EventDelete(COMP_YMODEM, EVENT_YM_TIMEOUT);
                }
                else
                {
                    /* 高速模式：收到数据包后直接回复ACK，让上位机立即发下个包过来 */
                    memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
                    Ymodem_SendCmd(YM_ACK, 0);
                }
            }
            ym_rx_data.packet_size = 0;
            ym_rx_data.packet_head.buffer = 0;
        }
        else
        {
            YMODEM_LOG("crc error\r\n");
            Ymodem_SendCmd(YM_NAK, 0);         // 如果crc错误, 让其重发
            // Ymodem_Abort(1); //abort
        }
    }
}

/**
  * @brief  串口中断接收回调函数
  * @note        
  * @param  data:收到的数据
  * @param  len:数据长度
  */
static void Ymodem_ReceiveCb(VirtualHardware_enum_t dev, void *data, uint32_t len)
{
    uint8_t *tmp = (uint8_t *)data;

    if (rx_handle == NULL)
    {
        return;
    }

    for (uint32_t i = 0; i < len; i++)
    {
        if (OSAL_RingBufferWrite(rx_handle, tmp + i) != SUCCESS)
        {
            return;
        }
    }
    OSAL_EventCreateFromISR(COMP_YMODEM);
}

/**
  * @brief  Ymodem初始化
  *
  * @note   注册RX回调、创建环形接收缓存
  */
static void Ymodem_Init(void)
{
    if (rx_handle == NULL)
    {
        rx_handle = OSAL_RingBufferCreate(YMODEM_RX_BUFF_LEN, 1);
        Device_RegisteredCB(v_uart, Ymodem_ReceiveCb);
    }
}

/**
  * @brief  超时处理逻辑
  *
  * @note   发出命令或者ACK后，上位机超时未下发新的包
  */
static void Ymodem_TimeoutProcess(void)
{
    if (ym_rx_data.status != YM_STA_IDLE)
    {
        if (ymodem_send_info.resend_times < 1) // 重发1次
        {
            YMODEM_LOG("Timeout, resend cmd\r\n");
            Ymodem_SendCmd(ymodem_send_info.cmd1, ymodem_send_info.cmd2);
            ymodem_send_info.resend_times++;
        }
        else
        {
            YMODEM_LOG("Timeout error\r\n");
            Ymodem_Abort(2);
            memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
        }
    }
}

/**
  * @brief  周期发出命令：C
  */
static void Ymodem_SendRequest(void)
{
    uint8_t cmd = YM_C;

    ym_rx_data.status = YM_STA_RX_NAME;
    Device_Write(v_uart, &cmd, 1, 0);
    __YMODEM_LOG("C\r\n");
}

/**
  * @brief  YModem发送ACK
  * @note   发出命令：ACK
  */
static int Ymodem_SendAck(void)
{
    YMODEM_LOG("Senk Ack\r\n");
    memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
    Ymodem_SendCmd(YM_ACK, 0);
    return 0;
}


/**
  * @brief  YModem发送NACK
  * @note   发出命令：NACK. 希望对方重发
  */
static int Ymodem_SendNak(void)
{
    YMODEM_LOG("Senk Nack\r\n");
    memset(&ymodem_send_info, 0, sizeof(ymodem_send_info));
    Ymodem_SendCmd(YM_NAK, 0);
    return 0;
}


/**
  * @brief  启动Ymodem传输
  * @note   间隔500ms发出命令：C
  */
static int Ymodem_Start(void)
{
    YMODEM_LOG("Ymodem_Start\r\n");
    Device_Enable(v_uart);
    OSAL_EventRepeatCreate(COMP_YMODEM, EVENT_YM_SEND_C, 500, EVT_PRIORITY_MEDIUM);
    return 0;
}

/**
  * @brief  Ymodem停止
  *
  * @note   应用层停止ymodem传输
  */
static int Ymodem_Stop(void)
{
    YMODEM_LOG("Ymodem_Stop\r\n");
    Ymodem_Abort(0);
    return 0;
}


static int Ymodem_DisableHardWare(void)
{
    Device_Disable(v_uart);
    return 0;
}

/**
  * @brief  切换ymodem数据通道
  *
  * @note   ymodem切换到前后板的串口进行通信（厂内升级时用到）
  */
static int Ymodem_SwitchChannel(void)
{
    YMODEM_LOG("Switch data channel\r\n");
    Device_ConfigCB(v_uart, DISABLE);
    
    ymodem_slow_mode = SET;
    v_uart = vUART_1;
    Device_RegisteredCB(v_uart, Ymodem_ReceiveCb);
    Device_SetUartBaudrate(v_uart, 921600);
    return 0;
}


/**
  * @brief  禁止ymodem自动回包
  *
  * @note   如: 指静脉的OTA时, 模块回包间隔不规整, 这里不发ACK, 等指静脉回包后再发ACK
  */
static int Ymodem_DisAutoAck(void)
{
    dfu_auto_ack = false;
    return 0;
}


/**
  * @brief  ymodem设置回复ack时间
  * @param  
  * @note   门磁等通过转发方式通信，需要延长ack时间
  */
static int Ymodem_SetAckTime(uint8_t devNum)
{
    if(devNum == DFU_DEVNUM_DOOR_SENSOR)
    {
        YmodemAckTime = KOS_PARAM_YMODEM_ACK_TIME + 100;    /* 门磁因为由主锁进行转发，所以需要增加时间 */
    }
    else
    {
        YmodemAckTime = KOS_PARAM_YMODEM_ACK_TIME;
    }
           
    return 0;
}

/**
  * @brief  Ymodem任务函数
  *
  * @note   1.任务函数内不能写阻塞代码
  *         2.任务函数每次运行只处理一个事件
  *         
  * @param  event：当前任务的所有事件
  *
  * @return 返回未处理的事件
  */
static uint32_t Ymodem_Task(uint32_t event)
{
	if (event & EVENT_SYS_START)
	{
        Ymodem_Init();
        SYS_API(Ymodem_Start);
        SYS_API(Ymodem_Stop);
        SYS_API(Ymodem_SwitchChannel);
        SYS_API(Ymodem_SendAck);  
        SYS_API(Ymodem_SendNak);          
        SYS_API(Ymodem_DisAutoAck);               
        SYS_API(Ymodem_SetAckTime);
        SYS_API(Ymodem_DisableHardWare);
        return ( event ^ EVENT_SYS_START );
    }

    if (event & EVENT_SYS_ISR)
    {
        Ymodem_ParseRxData();
        return ( event ^ EVENT_SYS_ISR );
    }

    if (event & EVENT_YM_SEND_ACK)
    {
        Ymodem_SendAck();
        return ( event ^ EVENT_YM_SEND_ACK );
    }

    if (event & EVENT_YM_SEND_C)
    {
        Ymodem_SendRequest();
        return ( event ^ EVENT_YM_SEND_C );
    }

    if (event & EVENT_YM_TIMEOUT)
    {
        Ymodem_TimeoutProcess();
        return ( event ^ EVENT_YM_TIMEOUT );
    }
    if (event & EVENT_SYS_SLEEP)
    {
        Device_Disable(v_uart);
        return ( event ^ EVENT_SYS_SLEEP );
    }
    return 0;
}
COMPONENT_TASK_EXPORT(COMP_YMODEM, Ymodem_Task, 0);
